package com.saas;

import cn.hutool.core.util.ClassUtil;
import com.saas.annotation.BlockedHandler;
import com.saas.util.JwtUtil;
import com.saas.util.PropertyUtil;
import com.saas.util.VertxUtil;
import com.saas.vertx.common.RouterVerticle;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.CompositeFuture;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.eventbus.EventBusOptions;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.dropwizard.DropwizardMetricsOptions;
import io.vertx.ext.dropwizard.MetricsService;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import org.jose4j.jwt.MalformedClaimException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: lzqing
 * @Description: 核心启动类 作用：启动参数配置、jwt校验、根据配置的包扫描包下的Vertical自动发布、统一异常处理
 * @Date: Created in 2021/6/6
 */
@SpringBootApplication(scanBasePackages = {"com.saas"})
public class AppLauncher extends AbstractVerticle implements RouterVerticle {

    private static final Logger logger = LoggerFactory.getLogger(AppLauncher.class.getName()); // slf4j日志记录器
    private static final String tcpFastOpen = PropertyUtil.getProperty("vertx.service.tcpFastOpen");
    private static final String tcpCork = PropertyUtil.getProperty("vertx.service.tcpCork");
    private static final String tcpQuickAck = PropertyUtil.getProperty("vertx.service.tcpQuickAck");
    private static final String reusePort = PropertyUtil.getProperty("vertx.service.reusePort");
    private static final String port = PropertyUtil.getProperty("vertx.service.port");
    JsonObject config = new JsonObject();

    public static void main(String[] args) throws IOException {
        SpringApplication.run(AppLauncher.class, args);
    }

    @EventListener
    public void deployVerticles(ApplicationReadyEvent event) {
        EventBusOptions eventBusOptions = new EventBusOptions();
        //便于调试 设定超时等时间较长 生产环境建议适当调整
        eventBusOptions.setConnectTimeout(1000 * 60 * 30);

        VertxOptions vertxOptions = new VertxOptions().setWorkerPoolSize(Runtime.getRuntime().availableProcessors())
                .setEventBusOptions(eventBusOptions)
                .setBlockedThreadCheckInterval(999999L)
                .setBlockedThreadCheckIntervalUnit(TimeUnit.SECONDS)
                .setWarningExceptionTime(999999L)
                .setWarningExceptionTimeUnit(TimeUnit.SECONDS)
                .setMaxWorkerExecuteTime(999999L)
                .setMaxWorkerExecuteTimeUnit(TimeUnit.SECONDS)
                .setMaxEventLoopExecuteTime(999999L)
                .setMaxEventLoopExecuteTimeUnit(TimeUnit.SECONDS).setMetricsOptions(
                        new DropwizardMetricsOptions().setEnabled(true)
                );
        Vertx vertx = Vertx.vertx(vertxOptions);
        VertxUtil.init(vertx);
        // 创建指标服务
        MetricsService metricsService = MetricsService.create(VertxUtil.getVertxInstance());

        // 获取当前的指标服务
        JsonObject json = metricsService.getMetricsSnapshot(VertxUtil.getVertxInstance());
        logger.info("【------当前的指标服务:【{}】------】", json);

        config.put("tcpFastOpen", tcpFastOpen);
        config.put("tcpCork", tcpCork);
        config.put("tcpQuickAck", tcpQuickAck);
        config.put("reusePort", reusePort);
        VertxUtil.getVertxInstance().deployVerticle(new AppLauncher(), new DeploymentOptions()
                .setConfig(config));
    }

    private void addHandler(Route route, Handler<RoutingContext> handler) {
        if (isBlocked(handler)) {
            route.blockingHandler(handler);
            route.handler(BodyHandler.create());
        } else {
            route.handler(BodyHandler.create());
            route.handler(handler);
        }
    }

    private boolean isBlocked(Handler<?> handler) {
        BlockedHandler an = handler.getClass().getAnnotation(BlockedHandler.class);
        return an != null;
    }

    @Override
    public void start() throws Exception {
        super.start();
        String deployPackage = PropertyUtil.getProperty("vertx.verticle.package");
        String[] split = deployPackage.split(",");
        Set<Class<?>> classes = new HashSet<>();
        Arrays.stream(split).forEach(str -> {
            final Set<Class<?>> clazz = ClassUtil.scanPackage(str);
            classes.addAll(clazz);
        });
        List<String> verticlesList = classes.stream().filter(clazz ->
                clazz.getSimpleName().contains(PACKAGE_FILTER_SUFFIX)
        ).map(clazz -> clazz.getName()
        ).collect(Collectors.toList());

        List<Future> futures = verticlesList.stream()
                .map(name -> Future.<String>future(r ->
                        VertxUtil.getVertxInstance().deployVerticle(name, new DeploymentOptions().setConfig(config), r))
                ).collect(Collectors.toList());

        CompositeFuture.all(futures).onComplete(res -> {
            Router subUrlAPI = Router.router(VertxUtil.getVertxInstance());
            routers.forEach(r -> {
                Handler<RoutingContext> handler = r.getHandler();
                String api = r.getApi();
                r.getSaaSHttpMethods().stream().forEach(httpMethod -> {
                    switch (httpMethod) {
                        case GET:
                            addHandler(
                                    subUrlAPI.get(api),
                                    handler
                            );
                            break;

                        case POST:
                            addHandler(
                                    subUrlAPI.post(api),
                                    handler
                            );
                            break;

                        case PUT:
                            addHandler(
                                    subUrlAPI.put(api),
                                    handler
                            );
                            break;

                        case HEAD:
                            addHandler(
                                    subUrlAPI.head(api),
                                    handler
                            );
                            break;

                        case PATCH:
                            addHandler(
                                    subUrlAPI.patch(api),
                                    handler
                            );
                            break;

                        case TRACE:
                            addHandler(
                                    subUrlAPI.trace(api),
                                    handler
                            );
                            break;

                        case DELETE:
                            addHandler(
                                    subUrlAPI.delete(api),
                                    handler
                            );
                            break;

                        case CONNECT:
                            addHandler(
                                    subUrlAPI.connect(api),
                                    handler
                            );
                            break;

                        case OPTIONS:
                            addHandler(
                                    subUrlAPI.options(api),
                                    handler
                            );
                            break;

                        case OTHER:
                            // ignore
                            break;
                    }
                });
            });

            Router mainRouter = Router.router(VertxUtil.getVertxInstance());

            mainRouter.route("/api/*").order(-1).handler(ctx -> {

                String accessToken = ctx.request().getHeader("accessToken");
                try {
                    String allowlistStr = PropertyUtil.getProperty("jwt.allowlist.path");
                    String[] allowlist = allowlistStr.split(",");
                    boolean isAllow = Arrays.stream(allowlist).anyMatch(e -> ctx.request().uri().contains(e));
                    if (isAllow) {
                        ctx.next();
                    } else {
                        if (JwtUtil.checkJwt(accessToken)) {
                            ctx.next();
                        } else {
                            ctx.json(new JsonObject().put("test", "你不合法呀"));
                        }
                    }
                } catch (MalformedClaimException e) {
                    e.printStackTrace();
                }
            });

            mainRouter.mountSubRouter("/api", subUrlAPI);

            VertxUtil.getVertxInstance().createHttpServer(new HttpServerOptions()
                    .setTcpFastOpen(Boolean.valueOf(tcpFastOpen))
                    .setTcpCork(Boolean.valueOf(tcpCork))
                    .setTcpQuickAck(Boolean.valueOf(tcpQuickAck))
                    .setReusePort(Boolean.valueOf(reusePort))).requestHandler(mainRouter).listen(Integer.valueOf(port), server -> {
                if (server.succeeded()) {
                    if (logger.isInfoEnabled()) {
                        logger.info("【------vertx createHttpServer was successful finally,listenned on port:【{}】", port);
                    }
                } else {
                    server.cause().printStackTrace();
                    logger.error("【------Server failed:【{}】------】", server.cause());
                }
            });
        }).onFailure(handler -> {
            try {
                logger.error("【------Server failed:【{}】------】", handler.getCause());
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("【------Server Exception:【{}】------】", e);
            }
        }).onSuccess(handler -> {
            if (logger.isInfoEnabled()) {
                logger.info("【------Server started success on port:【{}】------】", port);
            }
        });
    }
}
